package com.globalwave.pos.sale.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.globalwave.base.BaseServiceImpl;
import com.globalwave.common.ArrayOrderList;
import com.globalwave.common.ArrayPageList;
import com.globalwave.common.C;
import com.globalwave.common.U;
import com.globalwave.common.cache.CodeHelper;
import com.globalwave.common.exception.BusinessException;
import com.globalwave.customer.service.CustomerProfileBO;
import com.globalwave.pos.POSCodeHelper;
import com.globalwave.pos.inventory.entity.ProductItem;
import com.globalwave.pos.inventory.entity.ProductItemSO;
import com.globalwave.pos.payment.entity.PaymentLine;
import com.globalwave.pos.payment.entity.PaymentMethod;
import com.globalwave.pos.payment.entity.RecievableDetailInf;
import com.globalwave.pos.payment.service.Payable;
import com.globalwave.pos.payment.service.PaymentBO;
import com.globalwave.pos.sale.entity.SalesOrder;
import com.globalwave.pos.sale.entity.SalesOrderLine;
import com.globalwave.pos.sale.entity.SalesOrderSO;
import com.globalwave.system.entity.SessionUser;


@Service("salesOrderBO")
@Scope("prototype")
@Transactional
public class SalesOrderBO extends BaseServiceImpl implements Payable{

	
	private SalesOrder order = null ;
	private SalesOrderLineBO salesOrderLineBO ;
	private PaymentBO paymentBO = null;
	private CustomerProfileBO customerBO = null ;

	public SalesOrder newOrder() throws Exception {
		order = new SalesOrder() ;
		order.setPayment_progrss(C.PROGRSS_NOT_START) ;
		order.setPayment_times(C.ZERO_SHORT) ;

		order.setOriginal_price(C.ZERO_DOUBLE);
		order.setDiscount_price(C.ZERO_DOUBLE) ;
		order.setPrice_1(C.ZERO_DOUBLE) ;
		order.setPrice_2(C.ZERO_DOUBLE) ;
		order.setNation_tax(C.ZERO_DOUBLE) ;
		order.setLocation_tax(C.ZERO_DOUBLE) ;
		order.setRecievable_amount(C.ZERO_DOUBLE) ;
		order.setRecieved_amount(C.ZERO_DOUBLE) ;
		
		
		order.setShipment_progrss(C.PROGRSS_NOT_START) ;
		order.setShipment_times(C.ZERO_SHORT) ;
		order.setShipped_quantity(C.ZERO_INTEGER) ;
		order.setTna_quantity(C.ZERO_INTEGER) ;
		order.setOrder_quantity(C.ZERO_INTEGER) ;
		
		order.setOrder_progrss(C.PROGRSS_NOT_START) ;
		
		SessionUser user = SessionUser.get() ;
		order.setWarehouse_code(user.getShop_code()) ;
		order.setShop_code(user.getShop_code()) ;
		order.setTerminal_code(user.getTerminal_code()) ;
		order.setRegion_code(user.getUser().getRegion_code()) ;
		order.setStaff_number(user.getUser().getLogin_id()) ;
		order.setSale_date(user.getSale_date()) ;
		order.setOrder_timestamp(U.currentTimestamp()) ;
		
		order.setStatus_(C.STATUS_ACTIVE) ;
		
		return this.order ;
	}
	
	public SalesOrder get(SalesOrder order) throws Exception {
		this.order = (SalesOrder) jdbcDao.find(order) ;
		
		return this.order;
	}
	
	public SalesOrder getWithLines(SalesOrder order) throws Exception {
		this.order = (SalesOrder) jdbcDao.find(order) ;
		
		if (this.order == null) {
			throw new BusinessException("") ;
		}
		
		Long orderId = this.order.getId() ;
		List<SalesOrderLine> lines = getSalesOrderLineBO().getLines(orderId) ;
		this.order.setLines(lines);
		
		for (SalesOrderLine line:lines) {
			line.setSalesOrder(this.order) ;
		}

		List<PaymentLine> payments = getPaymentBO().getLinesBySalesOrder(orderId) ;
		this.order.setPaymentLines(payments) ;

		this.order.setSubscriber(getCustomerBO().get(this.order.getSubscriber_number())) ;
		this.order.setConsignee(getCustomerBO().get(this.order.getConsignee_number())) ;
		
		return this.order;
	}
	
	
	public ArrayPageList<SalesOrder> query(
			SalesOrderSO criterion,
            int pageIndex, 
            int pageSize, 
            ArrayOrderList orderList) throws Exception {
        
        if (orderList == null) {
            orderList = new ArrayOrderList() ;
            orderList.addDesc("id") ;
        }
        
        return (ArrayPageList<SalesOrder>)jdbcDao.query(criterion, pageIndex, pageSize, orderList, SalesOrder.class);
	}

	public SalesOrder addItem(SalesOrder order, ProductItemSO itemSo) throws Exception {
		ProductItem item = new ProductItem() ;
		item.setItem_number(itemSo.getItem_number()) ;
		
		return addItem(order, item, itemSo.getOrder_quantity()) ;
	}
	
	public SalesOrder addItem(SalesOrder order, ProductItem item, Integer quantity) throws Exception {
		
		if (order == null) {
			newOrder() ;
		} else {
			this.order = order ;
		}
		
		getSalesOrderLineBO().add(item, quantity) ;
		
		return this.order;
	}
	
	
	public void comfirm(SalesOrder order) throws Exception  {
		this.order = order ;
		order.setOid(POSCodeHelper.getOid(order.getType_(), order.getSale_date())) ;
		this.jdbcDao.insert(order) ;
		
		getSalesOrderLineBO() ;
		
		for (SalesOrderLine line:order.getLines()) {
			salesOrderLineBO.confirm(line) ;
		}
		
	}
	

	public void recomfirm(SalesOrder order) throws Exception  {
		this.order = order ;
	}


	public void ship(SalesOrder shipmentOrder) throws Exception  {
		
		if (this.order == null) {
		    this.order = get(shipmentOrder) ;// renew order from database
		}
		
		if (this.order.getLines().size() != shipmentOrder.getLines().size()) {
			throw new BusinessException("lines.size.not.eqaul") ;
		}
		
		this.order.setOperate(SalesOrder.OPERATE_UPDATE) ;
		
		this.order.setShipment_times(
				(short)(this.order.getShipment_times().shortValue() + 1)) ;

		List<SalesOrderLine> lines = this.order.getLines() ;
		
		List<SalesOrderLine> shipmentsLines = shipmentOrder.getLines() ;
		
		for (int i = shipmentsLines.size() - 1; i >= 0 ; i --) {
			salesOrderLineBO.ship(lines.get(i), shipmentsLines.get(i)) ;// if all of lines shiped
		}

		int quantity = this.order.getShipped_quantity() ;
		int tna = this.order.getTna_quantity() ;
		
		if (this.order.getOrder_quantity() == (quantity + tna)) {
			order.setShipment_progrss(C.PROGRSS_END) ;
			
			if (order.getPayment_progrss() == C.PROGRSS_END) {
				order.setOrder_progrss(C.PROGRSS_END) ;
			}
		}
		
		if (order.getShipment_progrss() == C.PROGRSS_NOT_START) {
			order.setShipment_progrss(C.PROGRSS_START) ;
		}
		
		this.jdbcDao.update(this.order) ;
	}

	public void pay(SalesOrder paymentOrder) throws Exception  {
		int size = this.order.getLines().size() ;
		List<RecievableDetailInf> details = new ArrayList<RecievableDetailInf>(size) ;
		
		for (SalesOrderLine d :this.order.getLines()) {
			details.add(d) ;
		}
		
		getPaymentBO().checkOut(
				this, 
				paymentOrder.getPaymentLines(), 
				details, 
				C.REF_SOURCE_SALES_ORDER, 
				this.order.getId()) ;
		
		
		jdbcDao.update(this.order) ;
	}

	public void autoShip() throws Exception  {
		SalesOrder shipmentOrder = this.order.clone() ;
		
		List<SalesOrderLine> lines = new ArrayList<SalesOrderLine>(order.getLines().size()) ;
		shipmentOrder.setLines(lines) ;
		
		for (SalesOrderLine line:order.getLines()) {
			SalesOrderLine sLine = line.clone() ;
			lines.add(sLine) ;
			
			salesOrderLineBO.autoShip(sLine) ;
		}
		
		ship(shipmentOrder) ;
	}
	
	/**
	 * line中产品增加的数量对订单的影响更新
	 * 
	 * @param line
	 * @param quantity
	 * @throws Exception
	 */
	public void updateByLine(SalesOrderLine line, int quantity) throws Exception  {

		//this.order.getLines().add(line) ;

		if (!POSCodeHelper.isUseInnerAccount(line.getCurrency_code())) {
			order.setOriginal_price(order.getOriginal_price() + line.getUnit_orginal_price() * quantity);
			order.setDiscount_price(order.getDiscount_price() + line.getUnit_discount_price() * quantity) ;
			order.setPrice_1(order.getPrice_1() + line.getUnit_price1() * quantity) ;
			order.setPrice_2(order.getPrice_2() + line.getUnit_price1() * quantity) ;
			order.setNation_tax(order.getNation_tax() + line.getUnit_nation_tax() * quantity) ;
			order.setLocation_tax(order.getLocation_tax() + line.getUnit_location_tax() * quantity) ;
			order.setRecievable_amount(order.getRecievable_amount() + line.getUnit_discount_price() * quantity) ;	
			order.setOrder_quantity(order.getOrder_quantity() + quantity) ;
		}
	}

	public void updateByPaymentLine(PaymentLine line) throws Exception  {

		if (!POSCodeHelper.isUseInnerAccount(line.getCurrency_code())) {
			if (line.getPayment_amount() != null) {				
				order.setRecieved_amount(order.getRecieved_amount() + line.getPayment_amount()) ;
			}
		}
	}

	public void updateByPayment(List<PaymentLine>paymentLines, boolean isAllPaid) throws Exception {
		this.order.setPaymentLines(paymentLines) ;

		order.setPayment_times((short)(order.getPayment_times().intValue() + 1)) ;
		
		// ------

		if (isAllPaid) {
		    order.setPayment_progrss(C.PROGRSS_END) ;
		    if (order.getShipment_progrss() == C.PROGRSS_END) {
		    	order.setOrder_progrss(C.PROGRSS_END) ;
		    }
		}
		
		if (order.getPayment_progrss() == C.PROGRSS_NOT_START) {
			order.setPayment_progrss(C.PROGRSS_START) ;
		}
	}
	/**
	 * line中产品增加的数量对订单的影响更新
	 * 
	 * @param line
	 * @param quantity
	 * @throws Exception
	 */
	public void updateByShipmentLine(SalesOrderLine line) throws Exception  {
		
		int quantity = line.getShipped_quantity_total() ;
		int tna = line.getTna_quantity() ;
		
		order.setShipped_quantity(order.getShipped_quantity() + quantity);
		order.setTna_quantity(order.getTna_quantity() + tna) ;
	}
	
	public SalesOrder cancel(SalesOrder order)throws Exception  {

		SessionUser user = SessionUser.get() ;
		
		SalesOrder currentOrder = this.get(order) ;
		
		if (C.STATUS_CANCEL.equals(currentOrder.getStatus_())) {
			throw new BusinessException("order.is.canceled") ;
		}
		
		// cancel payment
		SalesOrderLine payment = new SalesOrderLine() ;
		payment.setStatus_(C.STATUS_CANCEL) ;
		payment.addInclusions("status_") ;
		
		SalesOrderLine paymentCriterion = new SalesOrderLine() ;
		paymentCriterion.setSales_order_id(order.getId()) ;
		
		this.jdbcDao.update(payment, paymentCriterion) ;
		
		// cancel line
		SalesOrderLine line = new SalesOrderLine() ;
		line.setStatus_(C.STATUS_CANCEL) ;
		line.addInclusions("status_") ;
		
		SalesOrderLine criterion = new SalesOrderLine() ;
		criterion.setSales_order_id(order.getId()) ;
		
		this.jdbcDao.update(line, criterion) ;
		
		// cancel header
		this.order.setStatus_(C.STATUS_CANCEL) ;		
		this.order.setCancel_region_code(user.getUser().getRegion_code()) ;
		this.order.setCancel_shop_code(user.getShop_code()) ;
		this.order.setCancel_terminal_code(user.getTerminal_code()) ;
		this.order.setCancel_staff_number(user.getUser().getLogin_id()) ;
		this.order.setCancel_sale_date(user.getSale_date()) ;
		this.order.setCancel_timestamp(U.currentTimestamp()) ;
		
		this.jdbcDao.update(this.order) ;
		
		return getWithLines(order) ;
	}
	
	public List<PaymentLine> getPayementMethods(SalesOrder order) throws Exception {
		this.order = order ;
		// get line currency code 
		Map<String, Double> codes = new HashMap<String, Double>() ;
		for (SalesOrderLine line:this.order.getLines()) {
			String code = line.getCurrency_code() ;
			if (codes.keySet().contains(code)) {
			    codes.put(code, codes.get(code) + line.getRecievable_amount()) ;
			} else {
				codes.put(code, line.getRecievable_amount()) ;
			}
		}
		
		// get payment metohd by currency
		List<PaymentMethod> methods = 
			CodeHelper.query("PAYMENT_METHOD", "currency_code", codes.keySet(), PaymentMethod.class) ;
		
		List<PaymentLine> lines = new ArrayList<PaymentLine>() ;
		
		for (PaymentMethod method:methods) {
			PaymentLine line = new PaymentLine() ;
			lines.add(line);
			String code = method.getCurrency_code() ;
			line.setCurrency_code(code) ;
			line.setPayment_method(method.getCode_()) ;
			line.setPayment_method_name(method.getName_()) ;
			
			if (!"CNY".equals(code)) {
			    line.setIs_editable(false) ;
			    line.setPayment_amount(codes.get(code)) ;
			} 
		}
		
		return lines ;
	}

	public void payAndAutoComplete(SalesOrder order, SalesOrder paymentOrder) throws Exception {
		this.order = order ;
		comfirm(order) ;
		pay(paymentOrder) ;
		autoShip() ;
	}

	public SalesOrder getOrder() {
		return order;
	}



	public void setOrder(SalesOrder order) {
		this.order = order;
	}



	public SalesOrderLineBO getSalesOrderLineBO() {
		if (salesOrderLineBO == null) {
			salesOrderLineBO = new SalesOrderLineBO() ;
			salesOrderLineBO.setJdbcDao(jdbcDao) ;
		    salesOrderLineBO.setSalesOrderBO(this) ;
		}
		return salesOrderLineBO;
	}



	public void setSalesOrderLineBO(SalesOrderLineBO salesOrderLineBO) {
		this.salesOrderLineBO = salesOrderLineBO;
	}
	
	 

	public PaymentBO getPaymentBO() {
		if (paymentBO == null) {
			paymentBO = (PaymentBO) CodeHelper.getAppContext().getBean("paymentBO") ;
		}
		return paymentBO;
	}

	public void setPaymentBO(PaymentBO paymentBO) {
		this.paymentBO = paymentBO;
	}
	
	public CustomerProfileBO getCustomerBO() {
		if (customerBO == null) {
			customerBO = (CustomerProfileBO) CodeHelper.getAppContext().getBean("customerProfileBO") ;
		}
		return customerBO;
	}

	
}
